/* not type checking this file because flow doesn't play well with Proxy */

import config from 'core/config'
import {warn, makeMap, isNative} from '../util/index'

let initProxy

if (process.env.NODE_ENV !== 'production') {
  const allowedGlobals = makeMap(
    'Infinity,undefined,NaN,isFinite,isNaN,' +
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
    'require' // for Webpack/Browserify
  )

  /**
   * 2019-05-17
   * 1.使用了实例未定义的方法或者变量，示例如下：
   *    const vm = new Vue({
   *        el: '#app',
   *        template: '<div>{{a}}</div>',
   *        data: {
   *            test: 1
   *        }
   *    })
   */
  const warnNonPresent = (target, key) => {
    warn(
      `Property or method "${key}" is not defined on the instance but ` +
      'referenced during render. Make sure that this property is reactive, ' +
      'either in the data option, or for class-based components, by ' +
      'initializing the property. ' +
      'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
      target
    )
  }

  const warnReservedPrefix = (target, key) => {
    warn(
      `Property "${key}" must be accessed with "$data.${key}" because ` +
      'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
      'prevent conflicts with Vue internals' +
      'See: https://vuejs.org/v2/api/#data',
      target
    )
  }

  const hasProxy =
    typeof Proxy !== 'undefined' && isNative(Proxy)

  if (hasProxy) {
    const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
    /**
     * 2019-05-17
     * 为 config.keyCodes 设置 set 代理，防止内置修饰符被覆盖
     */
    config.keyCodes = new Proxy(config.keyCodes, {
      set (target, key, value) {
        if (isBuiltInModifier(key)) {
          warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
          return false
        } else {
          target[key] = value
          return true
        }
      }
    })
  }

  const hasHandler = {
    /**
     * 2019-05-17
     * Proxy--has，可以拦截如下操作：
     *    属性查询: foo in proxy
     *    继承属性查询: foo in Object.create(proxy)
     *    with 检查: with(proxy) { (foo); }（关键点）
     *    Reflect.has()
     */
    has (target, key) {
      const has = key in target
      /**
       * 2019-05-17
       * isAllowed：允许访问全局变量、内部渲染函数（_c、_v 等），示例如下：
       *    <template>
       *        {{Number(b) + 2}}
       *    </template>
       */
      const isAllowed = allowedGlobals(key) ||
        (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
      if (!has && !isAllowed) {
        if (key in target.$data) warnReservedPrefix(target, key)
        /**
         * 2019-05-17
         * 使用了实例未定义的方法或者变量，详细见方法内
         */
        else warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }

  const getHandler = {
    get (target, key) {
      if (typeof key === 'string' && !(key in target)) {
        if (key in target.$data) warnReservedPrefix(target, key)
        else warnNonPresent(target, key)
      }
      return target[key]
    }
  }

  initProxy = function initProxy(vm) {
    /**
     * 2019-05-17
     * hasProxy：判断宿主环境是否支持 js 原生的 Proxy 特性
     */
    if (hasProxy) {
      // determine which proxy handler to use
      /**
       * 2019-05-17
       * 1.options.render._withStripped：属性只在测试代码中出现过，所以一般情况下这个条件都会为假
       *    使用 webpack 配合 vue-loader 的环境中，
       *    vue-loader 会借助 vuejs@component-compiler-utils 将 template 编译为不使用 with 语句包裹的遵循严格模式的 JavaScript，
       *    并为编译后的 render 方法设置 render._withStripped = true
       *    Stripped（剥去的）
       * 2.handlers = hasHandler（一般开发情况）
       */
      const options = vm.$options
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }
}

export {initProxy}
